import type { AppContext } from '../types/index.js';
import { logger } from '../utils/logger.js';
import { redis } from '../utils/redis.js';
import { config } from '../config/env.js';
import { UnauthorizedException } from '../types/exceptions.js';
import { SESSION_PREFIX, TOKEN_PREFIX, LoginUtil } from '../utils/login-util.js';

/**
 * 将路径模式转换为正则表达式
 * @param pattern 路径模式，支持 * 通配符
 * @returns 正则表达式
 */
function pathToRegex(pattern: string): RegExp {
  // 将 * 转换为 .* 以匹配任意字符
  const regexPattern = pattern
    .replace(/\*/g, '.*') // 将 * 转换为 .*
    .replace(/\//g, '\\/'); // 转义斜杠
  return new RegExp(`^${regexPattern}$`);
}

/**
 * 检查路径是否在白名单中
 * @param path 请求路径
 * @returns 是否在白名单中
 */
function isWhitelisted(path: string): boolean {
  return config.auth.whitelist.some(pattern => {
    try {
      const regex = pathToRegex(pattern);
      return regex.test(path);
    } catch (error) {
      logger.error('白名单路径正则转换失败', { pattern, error });
      return false;
    }
  });
}

/**
 * 从请求中获取认证信息
 */
function getAuthInfo(ctx: AppContext): { type: 'session' | 'token'; value: string } | null {
  // 1. 尝试从 cookie 中获取 session
  const sessionId = ctx.cookies.get(config.auth.session.key);
  if (sessionId) {
    return { type: 'session', value: sessionId };
  }

  // 2. 尝试从 Authorization 头获取 token
  const authHeader = ctx.headers.authorization;
  if (authHeader && typeof authHeader === 'string' && authHeader.startsWith(TOKEN_PREFIX)) {
    const token = authHeader.substring(TOKEN_PREFIX.length);
    return { type: 'token', value: token };
  }

  return null;
}

export const authMiddleware = () => {
  return async (ctx: AppContext, next: () => Promise<void>): Promise<void> => {
    // 检查是否是白名单路由
    if (isWhitelisted(ctx.path)) {
      await next();
      return;
    }

    // 获取登录信息
    const authInfo = getAuthInfo(ctx);
    let userInfo = null;

    if (authInfo) {
      if (authInfo.type === 'session') {
        // 从 Redis 获取 session 数据
        userInfo = await LoginUtil.getSessionFromRedis(authInfo.value);

        if (userInfo) {
          // 更新 session 过期时间
          await redis.expire(
            `${SESSION_PREFIX}${authInfo.value}`,
            Math.floor(config.auth.session.maxAge / 1000)
          );

          // 刷新 cookie 过期时间
          ctx.cookies.set(config.auth.session.key, authInfo.value, {
            maxAge: config.auth.session.maxAge,
            httpOnly: config.auth.session.httpOnly,
            signed: config.auth.session.signed,
          });
        }
      } else {
        // 验证 JWT token
        userInfo = LoginUtil.verifyToken(authInfo.value);

        // 如果 token 即将过期（小于 1 小时），自动刷新 token
        if (userInfo && userInfo.expired) {
          const expiresIn = userInfo.expired - Math.floor(Date.now() / 1000);
          if (expiresIn < 3600) {
            // 小于 1 小时
            const newToken = LoginUtil.createToken(userInfo);
            ctx.set('X-New-Token', newToken);
            logger.info('Token 即将过期，已自动刷新', {
              userId: userInfo.user_id,
            });
          }
        }
      }

      if (userInfo) {
        // 将用户信息注入到 context 中
        ctx.state.user = userInfo;
        await next();
      } else {
        // 认证失败，清除可能存在的无效认证信息
        if (authInfo.type === 'session') {
          await redis.del(`${SESSION_PREFIX}${authInfo.value}`);
          ctx.cookies.set(config.auth.session.key, null);
        }

        throw new UnauthorizedException('认证失败');
      }
    } else {
      throw new UnauthorizedException('认证失败');
    }
  };
};
